// CoreCalc, a spreadsheet core implementation
// ----------------------------------------------------------------------
// Copyright (c) 2006-2009 Peter Sestoft and others

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

//  * The above copyright notice and this permission notice shall be
//    included in all copies or substantial portions of the Software.

//  * The software is provided "as is", without warranty of any kind,
//    express or implied, including but not limited to the warranties of
//    merchantability, fitness for a particular purpose and
//    noninfringement.  In no event shall the authors or copyright
//    holders be liable for any claim, damages or other liability,
//    whether in an action of contract, tort or otherwise, arising from,
//    out of or in connection with the software or the use or other
//    dealings in the software.
// ----------------------------------------------------------------------

using System;
using SCG = System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using C5;
using Microsoft.ParallelArrays;
using FPA = Microsoft.ParallelArrays.FloatParallelArray;
using BPA = Microsoft.ParallelArrays.BoolParallelArray;
using IPA = Microsoft.ParallelArrays.IntParallelArray;
using PA = Microsoft.ParallelArrays.ParallelArrays;
using CoreCalc.SheetDefinedFunctions.Accellerator;
using System.Reflection.Emit;

namespace CoreCalc.SheetDefinedFunctions
{



  /// <summary>
  /// Management of sheet defined functions.
  /// </summary>
  static class SdfManager
  {
      public static Stopwatch testWatch = new Stopwatch();
    // Static tree dictionary to map an SDF name to an SdfInfo object.
    private static readonly IDictionary<String, SdfInfo> sdfNameToInfo
      = new TreeDictionary<String, SdfInfo>();
    // Static arrays to map an SDF index to a callable SdfDelegate and SdfInfo.
    // Poorer modularity than if we used a private ArrayList, but faster SDF calls.
    // Invariant: sdfInfos[i]==null || sdfInfos[i].index==i
    // Invariant: sdfDelegates.Length == sdfInfos.Length >= 1
    public static Delegate[] sdfDelegates = new Delegate[1];
    private static SdfInfo[] sdfInfos = new SdfInfo[1];
    private static int nextIndex = 0;

    // For generating code to call an SDF, in CGSdfCall and CGApply
    internal static readonly FieldInfo sdfDelegatesField
      = typeof(SdfManager).GetField("sdfDelegates");
    
    // For discovering edits to sheet-defined functions, and for coloring them
    public static readonly CellsUsedInFunctions cellToFunctionMapper
      = new CellsUsedInFunctions();
    
    public static MarkLevel MarkLevel
    {
      get { return CodeGenerate.markLevel; }
      set { CodeGenerate.markLevel = value; }
    }

    public static bool CreateFunction(string name, FullCellAddr outPutCell, SCG.List<FullCellAddr> inputCells)
    {
      return CreateFunction(name, outPutCell, inputCells.ToArray());
    }

    public static bool CreateFunction(string name, FullCellAddr outputCell, FullCellAddr[] inputCells)
    {
      name = name.ToUpper();
      // If the function exists and is not a placeholder, keep it.
      // Else it is a placeholder and will have its applier overwritten.
      Function oldFunction = Function.Get(name);
      if (oldFunction != null)
        if (!oldFunction.IsPlaceHolder)
          return false;
      // Registering the method before compilation allows the 
      // function to call itself recursively
      SdfInfo sdfInfo = Register(outputCell, inputCells, name); 
      // Console.WriteLine("Compiling {0} as #{1}", name, info.index);
      
      GenerateAccNodes(sdfInfo); 
      
      Update(sdfInfo, CompileSdf(sdfInfo));
      
  
      Applier applier = delegate(Sheet sheet, Expr[] es, int col, int row)
      {

          //TODO: Entrypoint for MS acellerator code generation!
         

        // Arity is checked in the SdfInfo.CallN methods
        switch (es.Length)
        {
          case 0:
            return sdfInfo.Call0();
          case 1:
            return sdfInfo.Call1(es[0].Eval(sheet, col, row));
          case 2:
            return sdfInfo.Call2(es[0].Eval(sheet, col, row), es[1].Eval(sheet, col, row));
          case 3:
            return sdfInfo.Call3(es[0].Eval(sheet, col, row), es[1].Eval(sheet, col, row),
                                 es[2].Eval(sheet, col, row));
          case 4:
            return sdfInfo.Call4(es[0].Eval(sheet, col, row), es[1].Eval(sheet, col, row),
                                 es[2].Eval(sheet, col, row), es[3].Eval(sheet, col, row));
          case 5:
            return sdfInfo.Call5(es[0].Eval(sheet, col, row), es[1].Eval(sheet, col, row),
                                 es[2].Eval(sheet, col, row), es[3].Eval(sheet, col, row),
                                 es[4].Eval(sheet, col, row));
          case 6:
            return sdfInfo.Call6(es[0].Eval(sheet, col, row), es[1].Eval(sheet, col, row),
                                 es[2].Eval(sheet, col, row), es[3].Eval(sheet, col, row),
                                 es[4].Eval(sheet, col, row), es[5].Eval(sheet, col, row));
          case 7:
            return sdfInfo.Call7(es[0].Eval(sheet, col, row), es[1].Eval(sheet, col, row),
                                 es[2].Eval(sheet, col, row), es[3].Eval(sheet, col, row),
                                 es[4].Eval(sheet, col, row), es[5].Eval(sheet, col, row),
                                 es[6].Eval(sheet, col, row));
          case 8:
            return sdfInfo.Call8(es[0].Eval(sheet, col, row), es[1].Eval(sheet, col, row),
                                 es[2].Eval(sheet, col, row), es[3].Eval(sheet, col, row),
                                 es[4].Eval(sheet, col, row), es[5].Eval(sheet, col, row),
                                 es[6].Eval(sheet, col, row), es[7].Eval(sheet, col, row));
          case 9:
            return sdfInfo.Call9(es[0].Eval(sheet, col, row), es[1].Eval(sheet, col, row),
                                 es[2].Eval(sheet, col, row), es[3].Eval(sheet, col, row),
                                 es[4].Eval(sheet, col, row), es[5].Eval(sheet, col, row),
                                 es[6].Eval(sheet, col, row), es[7].Eval(sheet, col, row),
                                 es[8].Eval(sheet, col, row));
          default:
            return ErrorValue.Make("Too many arguments");
        }
      };
      if (oldFunction != null) // ... and is not a placeholder
        oldFunction.Applier = applier;
      else
        new Function(name, applier);
      return true;
    }

    private static void GenerateAccNodes(SdfInfo sdfInfo)
    {
        sdfInfo.TopAccNode = AccExpressionBuilder.BuildExpression(sdfInfo.outputCell, sdfInfo);

    }

    /// <summary>
    /// Compiles entry code and body of a sheet-defined function
    /// </summary>
    /// <param name="info">The SdfInfo object describing the function</param>
    /// <returns></returns>
    private static Delegate CompileSdf(SdfInfo info)
    {
      // Make dependency graph containing all cells needed by the output cell
      DependencyGraph dpGraph = new DependencyGraph(info.outputCell, info.inputCells,
        delegate(FullCellAddr fca) { return fca.sheet[fca.ca]; });
      dpGraph.CollectPrecedents();

      // Topologically sort the graph in reverse calculation order; leave out constants
      TopologicalSorter sorter = new TopologicalSorter(dpGraph);
      HashedLinkedList<FullCellAddr> cellList = sorter.Toposort(info.outputCell);

      // Convert each Expr into a CGExpr while preserving order; inline single-use expressions
      TopoListToCGExprList generator = new TopoListToCGExprList(cellList, info.inputCells);

      cellToFunctionMapper.AddFunction(info, dpGraph.GetAllNodes());
      return generator.CreateSdfDelegate(dpGraph);
    }

    // Register, unregister, update and look up the SDF tables

    /// <summary>
    /// Allocate an index for a new SDF, but do not bind its SdfDelegate
    /// </summary>
    /// <param name="outputCell"></param>
    /// <param name="inputCells"></param>
    /// <param name="name"></param>
    /// <returns></returns>
    public static SdfInfo Register(FullCellAddr outputCell, FullCellAddr[] inputCells, string name)
    {
      name = name.ToUpper();
      SdfInfo sdfInfo = GetInfo(name);
      if (sdfInfo == null)
      { // New SDF, register it
        sdfInfo = new SdfInfo(outputCell, inputCells, name, nextIndex++);
        Debug.Assert(sdfInfo.index == nextIndex - 1);
        sdfNameToInfo[name] = sdfInfo;
        if (sdfInfo.index >= sdfDelegates.Length)
        {
          Debug.Assert(sdfDelegates.Length == sdfInfos.Length);
          // Reallocate sdfDelegates array
          Delegate[] newSdfs = new Delegate[2 * sdfDelegates.Length];
          Array.Copy(sdfDelegates, newSdfs, sdfDelegates.Length);
          sdfDelegates = newSdfs;
          // Reallocate sdfInfos array
          SdfInfo[] newSdfInfos = new SdfInfo[2 * sdfInfos.Length];
          Array.Copy(sdfInfos, newSdfInfos, sdfInfos.Length);
          sdfInfos = newSdfInfos;
        }
        sdfInfos[sdfInfo.index] = sdfInfo;
      }      
      return sdfInfo;
    }

    private static void Update(SdfInfo info, Delegate method)
    {
      sdfDelegates[info.index] = method;
    }

    private static readonly ErrorValue errorDeleted 
      = ErrorValue.Make("Function deleted");
    // TODO: Probably need one "deleted" delegate for each arity?
    private static readonly Delegate[] sdfDeleted
      = { (Fun<Value>)(delegate() { return errorDeleted; }) };

    private static void Unregister(SdfInfo info)
    {
      sdfNameToInfo.Remove(info.name);
      sdfDelegates[info.index] = sdfDeleted[info.Arity];
      sdfInfos[info.index] = null;
    }

    public static SdfInfo GetInfo(String name)
    {
      SdfInfo info;
      if (sdfNameToInfo.Find(name.ToUpper(), out info))
        return info;
      else
        return null;
    }

    public static SdfInfo GetInfo(int sdfIndex)
    {
      return sdfInfos[sdfIndex];
    }

    public static SCG.IEnumerable<SdfInfo> GetAllInfos()
    {
      return sdfNameToInfo.Values;
    }

    /// <summary>
    /// Regenerates the indicated SDF delegates
    /// </summary>
    /// <param name="methods"></param>
    public static void Regenerate(SCG.IEnumerable<string> methods)
    {
      foreach (string s in methods)
        Regenerate(s);
    }

    public static void Regenerate(String name)
    {
      SdfInfo info = GetInfo(name);
      if (info != null)
        Regenerate(info);
    }

    public static void RegenerateAll()
    {
      foreach (SdfInfo info in GetAllInfos())
        Regenerate(info);
    }

    /// <summary>
    /// Generate code again for the given SDF, recreate the corresponding 
    /// Function, and update the delegate table entry for this SDF
    /// </summary>
    /// <param name="methodName"></param>
    public static void Regenerate(SdfInfo info)
    {
      // Remove existing information
      cellToFunctionMapper.RemoveFunction(info);
      Function.Remove(info.name);
      // Rebuild and add it back
      CreateFunction(info.name, info.outputCell, info.inputCells);
    }

    public static void DeleteMethod(string methodName)
    {
      SdfInfo info = GetInfo(methodName);
      if (info != null)
      {
        Unregister(info);
        cellToFunctionMapper.RemoveFunction(info);
        Function.Remove(methodName);
      }
    }

    internal static string[] CheckForModifications(SCG.List<FullCellAddr> changedCells)
    {
      return cellToFunctionMapper.GetFunctionsUsingAddresses(changedCells).ToArray();
    }
  }

  /// <summary>
  /// An SdfInfo instance represents a compiled sheet-defined function (SDF).
  /// </summary>
  public class SdfInfo
  {
    public readonly FullCellAddr outputCell;
    public readonly FullCellAddr[] inputCells;
    public readonly string name;
    public readonly int index;                // Index into sdfDelegates array
    public readonly Type myType;              // 
    public readonly MethodInfo myInvoke;      // myType.Invoke(...)
    public AccExpr TopAccNode;

    public static readonly Type[] sdfDelegateType
      = { typeof(Fun<Value>),
          typeof(Fun<Value,Value>),
          typeof(Fun<Value,Value,Value>),
          typeof(Fun<Value,Value,Value,Value>),
          typeof(Fun<Value,Value,Value,Value,Value>),
          typeof(Fun<Value,Value,Value,Value,Value,Value>),
          typeof(Fun<Value,Value,Value,Value,Value,Value,Value>),
          typeof(Fun<Value,Value,Value,Value,Value,Value,Value,Value>),
          typeof(Fun<Value,Value,Value,Value,Value,Value,Value,Value,Value>),
          typeof(Fun<Value,Value,Value,Value,Value,Value,Value,Value,Value,Value>),
          typeof(Fun<Value,Value,Value,Value,Value,Value,Value,Value,Value,Value,Value>) };

    private static readonly MethodInfo[] sdfDelegateInvokeMethods;

    public static readonly Type[][] argumentTypes;

    static SdfInfo()
    {
      
      sdfDelegateInvokeMethods = new MethodInfo[sdfDelegateType.Length];
      for (int i = 0; i < sdfDelegateType.Length; i++) 
        sdfDelegateInvokeMethods[i] = sdfDelegateType[i].GetMethod("Invoke");
      argumentTypes = new Type[sdfDelegateType.Length][];
      for (int i = 0; i < argumentTypes.Length; i++)
      {
        argumentTypes[i] = new Type[i];
        for (int j = 0; j < i; j++)
          argumentTypes[i][j] = Value.type;
      }
    }

    internal SdfInfo(FullCellAddr outputCell, FullCellAddr[] inputCells, 
                     string name, int index)
    {
      this.outputCell = outputCell;
      this.inputCells = inputCells;
      this.name = name.ToUpper();
      this.index = index;
      this.myType = sdfDelegateType[Arity];
      this.myInvoke = sdfDelegateInvokeMethods[Arity];
    }

    static int CallId = 0;

    public Value GenerateAcceleratorMethod(params Value[] values)
    {
        ArrayList<float[,]> arguments = new ArrayList<float[,]>();
        for (int i = 0; i < values.Length; i++)
        {
            float[,] reValues = new float[1, 1];
            reValues[0, 0] = (float)NumberValue.ToDoubleOrNan(values[i]);
            arguments.Add(reValues);
        }
            return GenerateAcceleratorMethod(1, 1, arguments);
    }

    public Value GenerateAcceleratorMethod(int width , int height , params float[][] values)
    {
        //Reorganize data to fit texture size!
        //approx texture size
      //  Console.WriteLine("before factorization:  " + SdfManager.testWatch.Elapsed);
        int length = values[0].Length;
        int cols = (int)Math.Ceiling(Math.Sqrt(length));
        while (length % cols != 0)
            cols--;
        int rows = length / cols;

      //  Console.WriteLine("after factorization:  " + SdfManager.testWatch.Elapsed);
      //  Console.WriteLine("before reorganize:  " + SdfManager.testWatch.Elapsed);
        //reorganize data
        int arity = values.Length;
        ArrayList<float[,]> arguments = new ArrayList<float[,]>();
        int j, i,h=0;
        for (int a = 0; a < arity; a++,h=0)
        {
            float[,] reValues = new float[cols, rows];
             for (i = 0; i < cols; i++)
                for (j = 0; j < rows; j++, h++)
                     reValues[i, j] = values[a][h];
            arguments.Add(reValues);
        }
      //  Console.WriteLine("after reorganize:  " + SdfManager.testWatch.Elapsed);
        return GenerateAcceleratorMethod(width,height,arguments);
    }

    public Value GenerateAcceleratorMethod(int width, int height, ArrayList<float[,]> arguments)
    {

        AccInputInfo info = new AccInputInfo(arguments);
        FPA top = TopAccNode.GenerateFPAWithCache(info, ++CallId);

        if (top.GetRank() == 1)
        {
            float [] result = new float[top.GetLength(0)];
            ParallelMath.ParMath.Instance.evalTarget.ToArray(top, out result);
            //Should probably be returned as something like FUN<value>
            return new NumberValue((double)result[0]);
        }
        if (top.GetRank() == 2)
        {
            float[,] result;
            result = new float[top.GetLength(0), top.GetLength(1)];
            
          //  Console.WriteLine("before gpu:  " + SdfManager.testWatch.Elapsed);
         //   SdfManager.testWatch.Stop();
         //   Stopwatch s = new Stopwatch();
           // s.Start();
            ParallelMath.ParMath.Instance.evalTarget.ToArray(top, out result);
           // s.Stop();
          //  Console.WriteLine("GPU call:    "+s.Elapsed);
         //   SdfManager.testWatch.Start();
           // Console.WriteLine("after gpu:  " + SdfManager.testWatch.Elapsed);
            
            int first = arguments[0].GetLength(0), second = arguments[0].GetLength(1);

            Value[,] vResult = new Value[width, height];
           
            int j,i,h=0;
            for (i = 0; i < width; i++)
                for (j = 0; j < height; j++,h++)
                    vResult[i, j] = NumberValue.Make(result[h % first, (int)(h / first)]);
                    
          

           // Console.WriteLine("after value convert1:  " + SdfManager.testWatch.Elapsed);
            return new ArrayExplicit(vResult);
        }
        else
        {
            throw new Exception("MAP not happy!");
        }

    }
   

    public int Arity { get { return inputCells.Length; } }

    public Value Call0()
    {
      if (Arity != 0)
        return ErrorValue.argCountErrorValue;
      if(Function.target == Function.TargetPlatform.GPU)
          return GenerateAcceleratorMethod();
      else
        return ((Fun<Value>)(SdfManager.sdfDelegates[index]))();
    }

    public Value Call1(Value v0)
    {
      if (Arity != 1)
        return ErrorValue.argCountErrorValue;

      if (Function.target == Function.TargetPlatform.GPU)
          return GenerateAcceleratorMethod(v0);
      else
        return ((Fun<Value, Value>)(SdfManager.sdfDelegates[index]))(v0);
    }

    public Value Call2(Value v0, Value v1)
    {
      if (Arity != 2)
          return ErrorValue.argCountErrorValue;
      if (Function.target == Function.TargetPlatform.GPU)
          return GenerateAcceleratorMethod(v0,v1);
      else
        return ((Fun<Value, Value, Value>)(SdfManager.sdfDelegates[index]))(v0, v1);
    }

    public Value Call3(Value v0, Value v1, Value v2)
    {
      if (Arity != 3)
        return ErrorValue.argCountErrorValue;
      if (Function.target == Function.TargetPlatform.GPU)
          return GenerateAcceleratorMethod(v0,v1,v2);
      else
        return ((Fun<Value, Value, Value, Value>)(SdfManager.sdfDelegates[index]))(v0, v1, v2);
      
        
    }

    public Value Call4(Value v0, Value v1, Value v2, Value v3)
    {
      if (Arity != 4)
        return ErrorValue.argCountErrorValue;
      if (Function.target == Function.TargetPlatform.GPU)
          return GenerateAcceleratorMethod(v0,v1,v2,v3);
      else
        return ((Fun<Value, Value, Value, Value, Value>)(SdfManager.sdfDelegates[index]))(v0, v1, v2, v3);
    }

    public Value Call5(Value v0, Value v1, Value v2, Value v3, Value v4)
    {
      if (Arity != 5)
         return ErrorValue.argCountErrorValue;
      if (Function.target == Function.TargetPlatform.GPU)
          return GenerateAcceleratorMethod(v0,v1,v2,v3,v4);
      else
          return ((Fun<Value, Value, Value, Value, Value, Value>)(SdfManager.sdfDelegates[index]))(v0, v1, v2, v3, v4);
      
       
    }

    public Value Call6(Value v0, Value v1, Value v2, Value v3, Value v4, Value v5)
    {
      if (Arity != 6)
        return  ErrorValue.argCountErrorValue;
      if (Function.target == Function.TargetPlatform.GPU)
          return GenerateAcceleratorMethod(v0,v1,v2,v3,v4,v5);
      else
           return  ((Fun<Value, Value, Value, Value, Value, Value, Value>)(SdfManager.sdfDelegates[index]))(v0, v1, v2, v3, v4, v5);
      
        
    }

    public Value Call7(Value v0, Value v1, Value v2, Value v3, Value v4, Value v5, Value v6)
    {
      if (Arity != 7)

          if (Function.target == Function.TargetPlatform.GPU)
              return GenerateAcceleratorMethod(v0, v1, v2, v3, v4,v5,v6);
          else

          return ((Fun<Value, Value, Value, Value, Value, Value, Value, Value>)(SdfManager.sdfDelegates[index]))(v0, v1, v2, v3, v4, v5, v6);
      else
        return ErrorValue.argCountErrorValue;
    }

    public Value Call8(Value v0, Value v1, Value v2, Value v3, Value v4, Value v5, Value v6, Value v7)
    {
      if (Arity == 8)
        return ((Fun<Value, Value, Value, Value, Value, Value, Value, Value, Value>)(SdfManager.sdfDelegates[index]))(v0, v1, v2, v3, v4, v5, v6, v7);
      else
        return ErrorValue.argCountErrorValue;
    }

    public Value Call9(Value v0, Value v1, Value v2, Value v3, Value v4, Value v5, Value v6, Value v7, Value v8)
    {
      if (Arity == 9)
        return ((Fun<Value, Value, Value, Value, Value, Value, Value, Value, Value, Value>)(SdfManager.sdfDelegates[index]))(v0, v1, v2, v3, v4, v5, v6, v7, v8);
      else
        return ErrorValue.argCountErrorValue;
    }

    public override string ToString()
    {
      return String.Format("FUN {0} AT #{1}", name, index);
    }

    
  }
}